home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 June: Reference Library / Dev.CD Jun 94.toast / Periodicals / develop / develop Issue 11 / develop 11 code / MultiBuffer / MultiBuffer Source / DoubleBuffers.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-07-15  |  14.9 KB  |  394 lines  |  [TEXT/MPS ]

  1. /* 
  2. 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
  3. |         |         |         |         |         |         |         |         |         |         
  4.  
  5. */
  6.  
  7. /*
  8. **        DoubleBuffers
  9. **
  10. **            This module contains the routines that deal with Double Buffering.
  11. **
  12. **            (NMD)    9//91        created
  13. **            (NMD)    9/22/91        DoubleBuffer - the high level interface to double buffering a file
  14. **            (NMD)    9/22/91        AllocateBuffers - allocates a pair of same-sized buffers. Handy
  15. **                                    for headers and the actual double buffers.  Only leaves memory
  16. **                                    if it completes.
  17. **            (NMD)    9/22/91        SetUpDBPrivateMem - sets up memory and pointers for our private 
  18. **                                    data.  As with AllocateBuffers, only leaves memory allocated if
  19. **                                    it completes succesfully.
  20. **            (NMD)    9/22/91        FreeDBPrivateMem - Gets rid of any remaining memory allocated by
  21. **                                    SetUpDBPrivateMem. checks to make sure it isn't stomping all 
  22. **                                    over things (hopefully).
  23. **            (NMD)    9/22/91        PrimeBuffers - pre loads the buffers, so we have some data so start 
  24. **                                    the buffering process with.
  25. **            (NMD)    10/4/91        QueueFrame - queues up a bufferCmd followed by a CallBackCmd.  The 
  26. **                                    Callback queues up yet another bufferCmd and callback (if we're
  27. **                                    not out of data) so as to keep things chugging along.
  28. **            (NMD)    10/4/91        DBService - this is the callback routine that takes care of 
  29. **                                    queueing up another buffer/callback pair, switching and refilling
  30. **                                    the buffers.
  31. **            (NMD)    10/4/91        CompleteRead - the completion routine for the asynch read.  Set flags
  32. **                                    and convert the buffer from 2's compliment notation to binary offset.
  33. **            (NMD)   10/6/91        ***Changed from straight double buffering to multiple buffering model***
  34. **            (NMD)    10/10/91    Included a stripped down read parameter block at the beginning of 
  35. **                                    each header to avoid problems where the parameter block was busy
  36. **                                    when we tried to queue a read.
  37. **            (NMD)    1/17/92        Modified SetUpDBPrivateMem to be a little prettier - only one 
  38. **                                    return point now.
  39. **            (NMD)    1/17/92        Modified DoubleBuffer to have a more aesthetically pleasing
  40. **                                    countenance...
  41. */
  42.  
  43.  
  44. #pragma load "MacHeaders"
  45.  
  46.  
  47.  
  48. #ifndef __MAININCLUDES__
  49. #include "MainApp.h"
  50. #endif
  51.  
  52. #ifndef __DOUBLEBUFFERINCLUDES__
  53. #include "DoubleBuffer.h"
  54. #endif
  55.  
  56. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  57. // application globals
  58.  
  59. //    The sound channel used for playing a sound, duh.
  60. SndChannelPtr    gSndChan;
  61.  
  62. //    "Stop processing this" flag
  63. static char        gsStopFlag;
  64.  
  65.  
  66. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  67. // do all of the initialization necesary
  68.  
  69. OSErr InitDoubleBuffer(void)
  70. {
  71.     OSErr        err;
  72.  
  73.     gSndChan = nil;
  74.     err = SndNewChannel(&gSndChan, sampledSynth, 0, (SndCallBackProcPtr)DBService);
  75.     return (err);
  76. }
  77.  
  78. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  79. void CloseDoubleBuffer(void)
  80. {
  81.     OSErr        err;
  82.  
  83.     err = SndDisposeChannel(gSndChan, true);
  84. }
  85.  
  86.  
  87. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  88. // Allocates memory required for a PrivateDBInfoPtr
  89. PrivateDBInfoPtr SetUpDBPrivateMem(void)
  90. {
  91.     PrivateDBInfoPtr        dbInfo                        = nil;                // top level of structure
  92.     short                    index                         = 0;                // index for buffer elements
  93.     Boolean                 failure                        = false;            // allocation failure flag
  94.     long                    requiredMem;
  95.     long                    totalSize;
  96.     long                    contigSize;
  97.  
  98.  
  99.     //    Allocate space for our private information.  Allocate top level of the structure, then allocate space for the
  100.     //    header information and the associated buffers.  Just to make our life a little easier, we do a little bit of
  101.     //    pre-flighting to see if there is anywhere near enough memory.
  102.  
  103.     PurgeSpace (&totalSize, &contigSize);
  104.     
  105.     //    Compute required space for our buffers, variables and soundheaders
  106.     requiredMem = (sizeof (PrivateDBInfo) + kNumBuffers*(kBufferSize + sizeof (SoundHeader)));
  107.     Assert ((requiredMem > totalSize), "\pMemory preflight failed...");
  108.     
  109.     //    If there's enough memory, continue with allocating the space for dbPrivateInfo
  110.     if (requiredMem < totalSize) {
  111.         DebugMessage ("\pabout to allocate memory for dbInfo");
  112.         
  113.         //    Allocate memory for the root level structure
  114.         dbInfo = (PrivateDBInfoPtr) NewPtrClear (sizeof (PrivateDBInfo));
  115.         if (dbInfo != nil){
  116.         
  117.             dbInfo->signature = 'dbBf';                                        // mark this as ours
  118.             
  119.             do {                                                            // for each buffer...
  120.                 DebugMessage ("\psetting up memory for a SoundHeader");
  121.  
  122.                 // Allocate memory for the sound header
  123.                 dbInfo->buffers[index].header = (SoundHeaderPtr) NewPtrClear (sizeof(SoundHeader));
  124.                 
  125.                 // If we succesfully allocated memory for the header, allocate the associated buffer
  126.                 if  (dbInfo->buffers[index].header) {
  127.                     DebugMessage ("\psetting up memory for associated buffer");
  128.                     dbInfo->buffers[index].header->samplePtr = NewPtrClear (kBufferSize);
  129.                     
  130.                     //    Check to see if the allocation succeded - if not, fail.
  131.                     if (dbInfo->buffers[index].header->samplePtr == nil) {
  132.                         failure = true;
  133.                         break;
  134.                     }
  135.                 } else {                                                    //    if header alloc failed
  136.                     failure = true;                                            //      mark and leave
  137.                     break;
  138.                 }
  139.             } while (++index < kNumBuffers);
  140.         } else
  141.             failure = true;                                                    // main allocation failed
  142.     }
  143.     
  144.     Assert (failure, "\pran out of memory");
  145.     if (failure) {
  146.         FreeDBPrivateMem (dbInfo);                                            // free any allocated memory
  147.         return (nil);                                                        // return nothing
  148.     } else
  149.         return (dbInfo);                                                    // return built structure
  150. }
  151.  
  152. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  153. // deallocates all the memory used in a PrivateDBInfoPtr
  154. void FreeDBPrivateMem (void *freeSpace)
  155. {
  156.     short                    index                         = 0;                // index for buffer elements
  157.     PrivateDBInfoPtr         dbInfo                        = 0;
  158.  
  159.     dbInfo = (PrivateDBInfoPtr) freeSpace;                                    // easier access to elements
  160.     
  161.     if (dbInfo->signature == 'dbBf') {                                        // make sure this is ours
  162.     
  163.         //    Since we allocated dbInfo with NewPtrClear, we just need to check for a non-zero
  164.         //    value to know it's safe to free.  Let's not corrupt those master pointers...
  165.         if (dbInfo) {
  166.             DebugMessage ("\pabout to dispose of scads of memory");
  167.             do {                                                            // for each buffer rec
  168.                 if (dbInfo->buffers[index].header->samplePtr)                //   get rid of buffer
  169.                     DisposPtr (dbInfo->buffers[index].header->samplePtr);
  170.                 if (dbInfo->buffers[index].header)
  171.                     DisposPtr ((Ptr) dbInfo->buffers[index].header);        //   get rid of header
  172.             } while (++index < kNumBuffers);
  173.             DisposPtr ((Ptr) dbInfo);                                        // dispose of top level
  174.         }
  175.     }
  176. }
  177.  
  178. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  179. // does all the setup for double buffering a file, and starts the process going
  180. OSErr DoubleBuffer (SndChannelPtr chan, unsigned long fileRefNum, ProcPtr readproc, ProcPtr processproc,
  181.                         SoundHeaderPtr generalHeader, unsigned long playSize, long dataOffset, Ptr *privateData)
  182. {
  183.     OSErr                    err                            = noErr;            // error bucket
  184.     PrivateDBInfoPtr        dbInfo                        = nil;
  185.  
  186.     //     Clear the stop flag 
  187.     gsStopFlag = false;
  188.     
  189.     //    We're going to use a PrivateDBInfo structure to hold all the information we'll need
  190.     //  later on to do our double buffering.  The next several lines of code deal with allocating
  191.     //    space for it and it's members and initializing fields.
  192.     dbInfo = SetUpDBPrivateMem ();
  193.     if (dbInfo != nil) {
  194.         DebugMessage ("\p allocated dbInfo successfully");
  195.         
  196.         *privateData = (Ptr)dbInfo;
  197.         
  198.         //    Install the read procedure.  This is mandatory.  life is over if it's not present...
  199.         if (readproc == nil) {
  200.             Assert (readproc == nil,"\pNo readproc specified");
  201.         } else {
  202.             DebugMessage ("\p Have a valid readproc");
  203.             dbInfo->readProcPtr = readproc;
  204.     
  205.             //    Install the processing procedure (if any).  Lack of a processing procedure knocks one level
  206.             //    of interrupt processing out, so this is a good way to save time and decrease the minimum
  207.             //    buffer size...
  208.             dbInfo->processingProcPtr = processproc;
  209.         
  210.             dbInfo->refNum = fileRefNum;                                    // store file ref num
  211.             dbInfo->a5ref = SetCurrentA5 ();
  212.         
  213.             //    We're essentially going to take over the specified sound channel to do double buffering with;
  214.             //    as a result, we'll install our own callback and put out private data structures in the userInfo
  215.             //    field.  Just to be nice, we're even going to save the values that were there when we started,
  216.             //  in case we had something we didn't want stomped on in them...
  217.             if (chan->userInfo)                                                // Valid userInfo?
  218.                 dbInfo->oldUserInfo = chan->userInfo;                        //   save it
  219.             chan->userInfo = (long) dbInfo;                                    // pointer to our vars...
  220.  
  221.             DebugMessage ("\pAbout to prime buffers");
  222.             
  223.             dbInfo->bytesToGo = playSize;                                    // Set up play size.
  224.             dbInfo->fileDataStart = dataOffset;                                // Offset into data stream
  225.  
  226.             err = PrimeBuffers (dbInfo, generalHeader);                        // fill both buffers...
  227.             if (err != noErr) {
  228.                 //    If we got to here, we got one [ED:censored] of an error trying to read the buffers,
  229.                 //     so now we commit programatic Seppuku (hope this knife's sharp!!)
  230.                 Assert (err != noErr, "\pHit an error trying to Fill buffers");
  231.                 FreeDBPrivateMem (dbInfo);                                    // Say bye2 to memory
  232.             } else {
  233.                 DebugMessage ("\pSuccessfully primed buffers");
  234.                 //    So, presumably at least one of our buffers has been filled, so let's set the chain reaction in
  235.                 //    motion.  Note that there is a possibility that we got only one buffer half full.  No worries: the
  236.                 //    callback routine will handle that nicely!!
  237.             
  238.                 dbInfo->bCmd.cmd = bufferCmd;
  239.                 dbInfo->bCmd.param1 = nil;
  240.                 dbInfo->bCmd.param2 = (long) dbInfo->buffers[0].header;
  241.             
  242.                 dbInfo->cbCmd.cmd = callBackCmd;
  243.                 dbInfo->cbCmd.param1 = 0;
  244.                 dbInfo->cbCmd.param2 = nil;
  245.             
  246.                 err = QueueFrame (chan, dbInfo);
  247.                 DebugMessage ("\pJust finnished queueing up the first frame");
  248.             }
  249.         }
  250.     }
  251.     return (err);
  252. }
  253.  
  254. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  255. // Calling this function kills a play operation in progress
  256. void KillDoubleBuffer (void)
  257. {
  258.     gsStopFlag = true;
  259. }
  260.  
  261. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  262. // Queues up a bufferCmd followed by a callBackCmd.
  263. OSErr QueueFrame (SndChannelPtr chan, PrivateDBInfoPtr dbInfo)
  264. {
  265.     OSErr                     err                         = noErr;
  266.     register unsigned long    *length                        = nil;
  267.     register short            *flags                        = nil;
  268.  
  269.     //    "Why", you justifiably ask, "are you bothering to move these simple structure elemets into
  270.     //    temporary local variables?".  Well, MPW C genereates awful code for each of these compares and
  271.     //    assignments.  Since this routine is often executed at interrupt time, and is therefore quite
  272.     //    time-critical, I'm trying to squeze as much performance as possible out of our "Challenged"
  273.     //    compiler...   Also, it makes things a tad more readable...
  274.  
  275.     length = &(dbInfo->buffers[dbInfo->currentBuffer].header->length);
  276.     flags = &(dbInfo->buffers[dbInfo->currentBuffer].flags);
  277.  
  278.     if (*length && (*flags == kBufferReady)) {                                // if we have data & it's ready
  279.         err = SndDoCommand (chan,&dbInfo->bCmd,true);                        //  queue up current buffer
  280.         Assert ((err != noErr), "\pCouldn't queue up the buffer ;g");
  281.         if (err)                                                            //  Got an error??
  282.             return (err);                                                    //   Yeah - Bail.
  283.         else {
  284.             *flags = kBufferPlaying;                                        // Mark the buffer playing
  285.             if (*length == kBufferSize) {                                    // Unless this was the last
  286.                 dbInfo->cbCmd.param1 = dbInfo->currentBuffer;                //  buffer callback is for
  287.                 err = SndDoCommand (chan,&dbInfo->cbCmd,true);                //  queue up a callback
  288.                 Assert ((err != noErr), "\pCouldn't queue up the callback ;g");
  289.                 if (err)                                                    //  Check for errors
  290.                     return (err);                                            //    got one?  bail.
  291.             }
  292.         }
  293.     } else {
  294.         Assert ((*flags), "\pTried to queue a buffer that wasn't ready ;g");
  295.         Assert ((*length == 0), "\pTried to queue a buffer that was empty ;g");
  296.         return (channelBusy);
  297.     }
  298.  
  299.     dbInfo->currentBuffer = NextHeader (dbInfo->currentBuffer);                // go on to next buffer
  300.  
  301.     return (noErr);
  302. }
  303.  
  304.  
  305. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306. // DBService is the callback procedure that takes care of swapping buffers and
  307. // refilling them.
  308. pascal void DBService (SndChannelPtr chan,SndCommand* acmd)
  309. {
  310.     OSErr                     err                         = noErr;
  311.     PrivateDBInfoPtr        dbInfo                        = nil;
  312.     short                    usedBuffer                    = 0;
  313.     long                    oldA5                        = nil;
  314.  
  315.     dbInfo = (PrivateDBInfoPtr) chan->userInfo;                                // get private info ptr
  316.  
  317.     usedBuffer = acmd->param1;
  318.  
  319.     oldA5 = SetA5 (dbInfo->a5ref);
  320.     
  321.     //    If the play operation hasn't been canceled
  322.     if (gsStopFlag != true) {
  323.     
  324.         //    Use the bufferCmd from the next header
  325.         dbInfo->bCmd.param2 = (long)dbInfo->buffers[dbInfo->currentBuffer].header;
  326.         err = QueueFrame (chan, dbInfo);                                    // play the frame
  327.         Assert ((err), "\pcouldn't queue up buffer... ;g");
  328.         if (err) {
  329.             SetA5 (oldA5);
  330.             return;
  331.         }
  332.     
  333.         dbInfo->buffers[usedBuffer].flags = kBufferFilling;
  334.     
  335.         Assert ((dbInfo->buffers[usedBuffer].readPB.pb.ioResult == 1), "\pthe parameter block was busy ;g");
  336.         if (dbInfo->buffers[usedBuffer].readPB.pb.ioResult == 1) {
  337.             SetA5 (oldA5);
  338.             return;
  339.         }
  340.     
  341.         //    Now, refill the exhausted buffer with some fresh data.  Frankly, I haven't figured 
  342.         //  out what to do about errors returned at this point.  Suggestions welcome...
  343.         err = (*(dbInfo->readProcPtr)) (dbInfo, usedBuffer, true);
  344.     }
  345.     SetA5 (oldA5);
  346. }
  347.  
  348. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  349. // completion routine for asynchronous reads
  350. void CompleteRead (void)
  351. {
  352.     OSErr                             err                         = noErr;
  353.     ExtParmBlkPtr                    callPB                        = nil;
  354.     PrivateDBInfoPtr                dbInfo                        = nil;
  355.     long                            oldA5                        = nil;
  356.     register DeferredTask            *dt                            = nil;
  357.     register SampleBufferPtr        buffer                        = nil;
  358.  
  359.     err = getErr();                                                            // get the result
  360.     callPB = (ExtParmBlkPtr) getPB();                                        //   and our paramblock
  361.     dbInfo = (PrivateDBInfoPtr) callPB->userInfo;                            //   *and* our privates..
  362.  
  363.     //    Again, we're trying to squeeze a little extra speed out of our friend, the MPW C compiler.
  364.     //    We make lots of similar references to "dbInfo->buffers[callPB->headerNum].dt", so lets
  365.     //    move it down to a register pointer variable...
  366.     dt = &(dbInfo->buffers[callPB->headerNum].dt);
  367.     buffer = &(dbInfo->buffers[callPB->headerNum]);
  368.  
  369.     oldA5 = SetA5 (dbInfo->a5ref);
  370.  
  371.     switch (err) {
  372.         case eofErr :
  373.         case noErr :                                                        // harmless error codes
  374.             buffer->header->length = callPB->pb.ioActCount;                    // fill size in correctly
  375.             if (dbInfo->processingProcPtr) {
  376.                 buffer->flags = kBufferProcessing;                            // mark buffer processing
  377.                 dt->qLink = 0;
  378.                 dt->qType = dtQType;
  379.                 dt->dtFlags = 0;
  380.                 dt->dtReserved = 0;
  381.                 dt->dtAddr = dbInfo->processingProcPtr;
  382.                 dt->dtParam = (long) &(dbInfo->buffers[callPB->headerNum]);
  383.                 QuickDTInstall (&buffer->dt);
  384.             } else
  385.                 buffer->flags = kBufferReady;                                // mark buffer processing
  386.             break;
  387.         default:                                                            // some other error??
  388.             dbInfo->bytesToGo = nil;                                        //    kill off buffering
  389.             buffer->header->length = 0;
  390.     }
  391.     SetA5 (oldA5);
  392. }
  393.  
  394.